﻿#region Imports
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using CommonUtilities.Commands;
using CommonUtilities.Enums;
using CommonUtilities.Extensions;
using CommonUtilities.Models;
using CommonUtilities.Util;
using Microsoft.VisualBasic;
using PlayRandom.Models;
using PlayRandom.Views;
using FileIO = Microsoft.VisualBasic.FileIO;
using WinForms = System.Windows.Forms;
#endregion Imports

namespace PlayRandom.ViewModels
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public class MainViewModel : BaseViewModel, ILogger
    {
        #region Declarations
        /// <summary>
        /// ``RKD
        /// </summary>
        private const string PREFIX = "``RKD";
        /// <summary>
        /// *.*
        /// </summary>
        private const string PATTERN_ALL = "*.*";
        private const string PATTERN_FAV = PREFIX + "*";
        private const string IGNORE_EXTN = "INF,INI,DB,LNK,URL,NOMEDIA,NFO,BAK";

        private const string DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss.fff";

        /// <summary>
        /// Select a valid directory first..
        /// </summary>
        private const string SEL_VAL_DIR = "Select a valid directory first..";

        private const string PATH_MPCHC = "mpc-hc";
        private const string PATH_VLC = @"C:\Program Files (x86)\VideoLAN\VLC\vlc.exe";

        /// <summary>
        /// ' ,;|'
        /// </summary>
        private const string DELIM_TIMESTAMP_INPUT = " ,;|";
        /// <summary>
        /// Timestamp delimeter in INI '|'
        /// </summary>
        internal const char DELIM_TIMESTAMP_INI = '|';
        /// <summary>
        /// Timestamp delimeter in File Name ' '
        /// </summary>
        private const char DELIM_TIMESTAMP_FILENAME = ' ';
        /// <summary>
        /// Tab
        /// </summary>
        private const char DELIM_FIELDS = '\t';
        /// <summary>
        /// Timestamp delimeter for favourites in app '»'
        /// </summary>
        private const char DELIM_TIMESTAMP_FAVNAME = '»';
        /// <summary>
        /// ', '
        /// </summary>
        private const string DELIM_FILTERS = ", ";

        /// <summary>
        /// Black
        /// </summary>
        internal static readonly Brush FAV_COLOUR_DEFAULT = Brushes.Black;
        /// <summary>
        /// Green
        /// </summary>
        private readonly Brush FAV_COLOUR_RENAMED = Brushes.Green;
        /// <summary>
        /// Blue
        /// </summary>
        private readonly Brush FAV_COLOUR_INI = Brushes.Blue;
        /// <summary>
        /// Red
        /// </summary>
        private readonly Brush FAV_COLOUR_ERROR = Brushes.Red;
        /// <summary>
        /// Gray
        /// </summary>
        private readonly Brush FAV_COLOUR_PLAYED = Brushes.Gray;
        private readonly string favouritesFileName;

        private Filter filterDefault = new Filter { IsDefault = true };
        private SettingsView settingsView = null;

        string lastSelectedFavFile = String.Empty;
        internal ObservableCollection<string> bkpTimestamp;

        private readonly string settingsPath;
        internal static Action SaveSettings;
        internal string timestampBeforeUpdate = String.Empty;
        private string lastPathForListFav = String.Empty;

        private ObservableCollection<string> files_bkp = null;
        #endregion Declarations

        #region Command declarations
        public RelayCommand OpenSettingsCommand { get; private set; }
        public RelayCommand ListFavCommand { get; private set; }
        public RelayCommand PlayFavCommand { get; private set; }

        public RelayCommand AddPathCommand { get; private set; }
        public RelayCommand PlayRandomCommand { get; private set; }

        public RelayCommand AddSelectedRandomToFavCommand { get; private set; }
        public RelayCommand DeleteSelectedRandomCommand { get; private set; }
        public RelayCommand PlaySelectedRandomCommand { get; private set; }

        public RelayCommand SearchFavFocusCommand { get; internal set; }
        public RelayCommand FavFilesMouseDoubleClickCommand { get; private set; }
        public RelayCommand RenameFavCommand { get; private set; }
        public RelayCommand DeleteFavCommand { get; private set; }
        public RelayCommand FiltersDropDownClosedCommand { get; private set; }
        public RelayCommand FiltersDropDownOpenedCommand { get; private set; }
        public RelayCommand FiltersSelectionChangedCommand { get; private set; }
        public RelayCommand RefreshFiltersCommand { get; private set; }
        public RelayCommand IncludeFavCheckedCommand { get; private set; }
        public RelayCommand CopyPathCommand { get; private set; }
        public RelayCommand<string> DelPathCommand { get; private set; }
        public RelayCommand MigrateMouseEnterCommand { get; private set; } // RKD - doesn't open context menu on first time
        public RelayCommand UseFolderCommand { get; private set; }
        public RelayCommand FavOpenFolderCommand { get; private set; }
        public RelayCommand BrowseCommand { get; private set; }

        public RelayCommand FilterFileTypeCheckedCommand { get; private set; }
        public RelayCommand FilterAllCommand { get; private set; }
        public RelayCommand FilterNoneCommand { get; private set; }

        public RelayCommand UpdateTimestampCommand { get; private set; }
        public RelayCommand AddTimestampCommand { get; private set; }
        public RelayCommand SortTimestampCommand { get; private set; }
        public RelayCommand SaveTimestampCommand { get; private set; }
        public RelayCommand<string> DeleteTimestampCommand { get; private set; }
        public RelayCommand CloseTimestampWindowCommand { get; internal set; }

        public RelayCommand FavToDiskCommand { get; private set; }
        public RelayCommand FavFileRenameCommand { get; private set; }
        public RelayCommand FavExportCommand { get; private set; }

        public RelayCommand<string> TimestampMouseDoubleClickCommand { get; private set; }
        public RelayCommand<FileDetails> ScrollToViewCommand { get; internal set; }
        public RelayCommand DirPathFocusCommand { get; internal set; }
        #endregion Command declarations

        #region Properties
        private string _FavGroupHeaderText;
        public string FavGroupHeaderText
        {
            get { return _FavGroupHeaderText; }
            set { _FavGroupHeaderText = value; RaisePropertyChanged("FavGroupHeaderText"); }
        }

        private string _ListFavText;
        public string ListFavText
        {
            get { return _ListFavText; }
            set { _ListFavText = value; RaisePropertyChanged("ListFavText"); }
        }

        private string _PickFavText;
        public string PickFavText
        {
            get { return _PickFavText; }
            set { _PickFavText = value; RaisePropertyChanged("PickFavText"); }
        }

        public string SettingsText { get; set; }

        private Settings _AppSettings;
        public Settings AppSettings
        {
            get { return _AppSettings; }
            set { _AppSettings = value; RaisePropertyChanged("AppSettings"); }
        }

        private Dictionary<eMediaPlayer, string> _AllMediaPlayers = new Dictionary<eMediaPlayer, string>();
        public Dictionary<eMediaPlayer, string> AllMediaPlayers
        {
            get { return _AllMediaPlayers; }
            set { _AllMediaPlayers = value; RaisePropertyChanged("AllMediaPlayers"); }
        }

        private Dictionary<eRecycleOption, string> _AllDeleteOptions = new Dictionary<eRecycleOption, string>();
        public Dictionary<eRecycleOption, string> AllDeleteOptions
        {
            get { return _AllDeleteOptions; }
            set { _AllDeleteOptions = value; RaisePropertyChanged("AllDeleteOptions"); }
        }

        private Dictionary<eFavouritesOption, string> _AllFavouritesOptions = new Dictionary<eFavouritesOption, string>();
        public Dictionary<eFavouritesOption, string> AllFavouritesOptions
        {
            get { return _AllFavouritesOptions; }
            set { _AllFavouritesOptions = value; RaisePropertyChanged("AllFavouritesOptions"); }
        }

        private ObservableCollection<Filter> _AllFilters = new ObservableCollection<Filter>();
        public ObservableCollection<Filter> AllFilters
        {
            get { return _AllFilters; }
            set
            {
                _AllFilters = value; RaisePropertyChanged("AllFilters");
                RefreshFilterTooltip();
            }
        }

        private Dictionary<string, string> _AllPaths = new Dictionary<string, string>();
        public Dictionary<string, string> AllPaths
        {
            get { return _AllPaths; }
            set { _AllPaths = value; RaisePropertyChanged("AllPaths"); }
        }

        private string _DirPath;
        public string DirPath
        {
            get { return _DirPath; }
            set
            {
                AppSettings.SearchSettings.DirPath = _DirPath = FormatPath(value); RaisePropertyChanged("DirPath");
                IsPlayRandomEnabled = IsPlayFavEnabled = IsListFavEnabled = IsAddPathEnabled = Directory.Exists(_DirPath);

                if (AppSettings.SearchSettings.AutoCache) AppSettings.SearchSettings.CacheSearch = false; // Reset cache flag
            }
        }

        private string _DirPathAdded = String.Empty;
        public string DirPathAdded
        {
            get { return _DirPathAdded; }
            set
            {
                _DirPathAdded = FormatPath(value); RaisePropertyChanged("DirPathAdded");

                IsPlayRandomEnabled = IsPlayFavEnabled = IsListFavEnabled = Directory.Exists(_DirPathAdded);
                IsAddPathEnabled = Directory.Exists(_DirPathAdded) || _DirPathAdded.IsBlank();
            }
        }

        private ObservableCollection<FileDetails> _AllRandomFiles = new ObservableCollection<FileDetails>();
        public ObservableCollection<FileDetails> AllRandomFiles
        {
            get { return _AllRandomFiles; }
            set { _AllRandomFiles = value; RaisePropertyChanged("AllRandomFiles"); }
        }

        private FileDetails _SelectedRandomFile;
        public FileDetails SelectedRandomFile
        {
            get { return _SelectedRandomFile; }
            set { _SelectedRandomFile = value; RaisePropertyChanged("SelectedRandomFile"); }
        }

        public string FilePath_Org { get; set; }

        private ObservableCollection<FileDetails> _AllFavFiles = new ObservableCollection<FileDetails>();
        public ObservableCollection<FileDetails> AllFavFiles
        {
            get { return _AllFavFiles; }
            set { _AllFavFiles = value; RaisePropertyChanged("AllFavFiles"); }
        }

        private FileDetails _SelectedFavFile;
        public FileDetails SelectedFavFile
        {
            get { return _SelectedFavFile; }
            set
            {
                _SelectedFavFile = value; RaisePropertyChanged("SelectedFavFile");
                IsDeleteFavEnabled = _SelectedFavFile != null;

                StatusText = _SelectedFavFile.IsNull() ? "No favourites selected.." : _SelectedFavFile.ToString();
            }
        }

        private string _SelectedDelPath;
        public string SelectedDelPath
        {
            get { return _SelectedDelPath; }
            set { _SelectedDelPath = value; RaisePropertyChanged("SelectedDelPath"); }
        }

        private bool _IsAddPathChecked;
        public bool IsAddPathChecked
        {
            get { return _IsAddPathChecked; }
            set { _IsAddPathChecked = value; RaisePropertyChanged("IsAddPathChecked"); }
        }

        private bool _IsDelPathChecked;
        public bool IsDelPathChecked
        {
            get { return _IsDelPathChecked; }
            set { _IsDelPathChecked = value; RaisePropertyChanged("IsDelPathChecked"); }
        }

        private string _AddPathContent;
        public string AddPathContent
        {
            get { return _AddPathContent; }
            set { _AddPathContent = value; RaisePropertyChanged("AddPathContent"); }
        }

        private string _AddPathToolTip;
        public string AddPathToolTip
        {
            get { return _AddPathToolTip; }
            set { _AddPathToolTip = value; RaisePropertyChanged("AddPathToolTip"); }
        }

        private string _FavCountText;
        public string FavCountText
        {
            get { return _FavCountText; }
            set { _FavCountText = value; RaisePropertyChanged("FavCountText"); }
        }

        private string _FileCountText;
        public string FileCountText
        {
            get { return _FileCountText; }
            set { _FileCountText = value; RaisePropertyChanged("FileCountText"); }
        }

        private bool _IsMigrateContextMenuOpen;
        public bool IsMigrateContextMenuOpen
        {
            get { return _IsMigrateContextMenuOpen; }
            set { _IsMigrateContextMenuOpen = value; RaisePropertyChanged("IsMigrateContextMenuOpen"); }
        }

        private bool _IsExpandFavChecked;
        public bool IsExpandFavChecked
        {
            get { return _IsExpandFavChecked; }
            set { _IsExpandFavChecked = value; RaisePropertyChanged("IsExpandFavChecked"); }
        }

        private Filter _SelectedFilter;
        public Filter SelectedFilter
        {
            get { return _SelectedFilter; }
            set { _SelectedFilter = value; RaisePropertyChanged("SelectedFilter"); }
        }

        private string _FiltersToolTip;
        public string FiltersToolTip
        {
            get { return _FiltersToolTip; }
            set { _FiltersToolTip = value; RaisePropertyChanged("FiltersToolTip"); }
        }

        private bool _IsAdminNoPlay;
        public bool IsAdminNoPlay
        {
            get { return _IsAdminNoPlay; }
            set { _IsAdminNoPlay = value; RaisePropertyChanged("IsAdminNoPlay"); }
        }

        private bool _IsAddPathEnabled;
        public bool IsAddPathEnabled
        {
            get { return _IsAddPathEnabled; }
            set { _IsAddPathEnabled = value; RaisePropertyChanged("IsAddPathEnabled"); }
        }

        private bool _IsPlayRandomEnabled;
        public bool IsPlayRandomEnabled
        {
            get { return _IsPlayRandomEnabled; }
            set { _IsPlayRandomEnabled = value; RaisePropertyChanged("IsPlayRandomEnabled"); }
        }

        private bool _IsPlayFavEnabled;
        public bool IsPlayFavEnabled
        {
            get { return _IsPlayFavEnabled; }
            set { _IsPlayFavEnabled = value; RaisePropertyChanged("IsPlayFavEnabled"); }
        }

        private bool _IsListFavEnabled;
        public bool IsListFavEnabled
        {
            get { return _IsListFavEnabled; }
            set { _IsListFavEnabled = value; RaisePropertyChanged("IsListFavEnabled"); }
        }

        private bool _IsDeleteFavEnabled;
        public bool IsDeleteFavEnabled
        {
            get { return _IsDeleteFavEnabled; }
            set { _IsDeleteFavEnabled = value; RaisePropertyChanged("IsDeleteFavEnabled"); }
        }

        private string _RemFavCountText;
        public string RemFavCountText
        {
            get { return _RemFavCountText; }
            set { _RemFavCountText = value; RaisePropertyChanged("RemFavCountText"); }
        }

        private string _TimestampMediaPlayer;
        public string TimestampMediaPlayer
        {
            get { return _TimestampMediaPlayer; }
            set { _TimestampMediaPlayer = value; RaisePropertyChanged("TimestampMediaPlayer"); }
        }

        private bool _IsUpdateMode;
        public bool IsUpdateMode
        {
            get { return _IsUpdateMode; }
            set { _IsUpdateMode = value; RaisePropertyChanged("IsUpdateMode"); }
        }

        private bool _RepeatFavourites;
        public bool RepeatFavourites
        {
            get { return _RepeatFavourites; }
            set
            {
                _RepeatFavourites = value; RaisePropertyChanged("RepeatFavourites");
                AppSettings.FavouritesSettings.RepeatFavourites = _RepeatFavourites;
            }
        }

        private bool _IsTimestampInputBoxOnTop;
        public bool IsTimestampInputBoxOnTop
        {
            get { return _IsTimestampInputBoxOnTop; }
            set { _IsTimestampInputBoxOnTop = value; RaisePropertyChanged("IsTimestampInputBoxOnTop"); }
        }

        private bool _IsDirty;
        public bool IsDirty
        {
            get { return _IsDirty; }
            set { _IsDirty = value; RaisePropertyChanged("IsDirty"); }
        }

        public bool HasFavourites
        {
            get { return AllFavFiles.Any(); }
        }

        private string _FilteredFavCountText;
        public string FilteredFavCountText
        {
            get { return _FilteredFavCountText; }
            set { _FilteredFavCountText = value; RaisePropertyChanged("FilteredFavCountText"); }
        }

        private string _FileSearchText;
        public string FileSearchText
        {
            get { return _FileSearchText; }
            set
            {
                _FileSearchText = value; RaisePropertyChanged("FileSearchText");
                FilterFav(_FileSearchText);
            }
        }

        private ObservableCollection<FileDetails> _AllFavFiles_Filtered;
        public ObservableCollection<FileDetails> AllFavFiles_Filtered
        {
            get { return _AllFavFiles_Filtered; }
            set { _AllFavFiles_Filtered = value; RaisePropertyChanged("AllFavFiles_Filtered"); }
        }

        private string _WatermarkText;
        public string WatermarkText
        {
            get { return _WatermarkText; }
            set { _WatermarkText = value; RaisePropertyChanged("WatermarkText"); }
        }

        private bool? _IsSortAsc;
        public bool? IsSortAsc
        {
            get { return _IsSortAsc; }
            set
            {
                _IsSortAsc = value; RaisePropertyChanged("IsSortAsc");
                if (IsTimestampSelectorOpen) SortTimestampCommand.Execute(IsSortAsc);
            }
        }

        private bool _IsTimestampSelectorOpen;
        public bool IsTimestampSelectorOpen
        {
            get { return _IsTimestampSelectorOpen; }
            set { _IsTimestampSelectorOpen = value; RaisePropertyChanged("IsTimestampSelectorOpen"); }
        }

        private string _StatusText;
        public string StatusText
        {
            get { return _StatusText; }
            set
            {
                _StatusText = value; RaisePropertyChanged("StatusText");
                StatusAddnlInfo = String.Empty;
            }
        }

        private string _StatusAddnlInfo;
        public string StatusAddnlInfo
        {
            get { return _StatusAddnlInfo; }
            set { _StatusAddnlInfo = value; RaisePropertyChanged("StatusAddnlInfo"); }
        }
        #endregion Properties

        #region Constructors
        public MainViewModel()
        {
            FavGroupHeaderText = "♥ Favourites";
            ListFavText = "List favourites ♥";
            PickFavText = "Pick a fav ♥";
            WatermarkText = "Search files";

            settingsView = new SettingsView(this, SizeToContent.WidthAndHeight);
            settingsPath = Directory.GetCurrentDirectory() + @"\Settings.ini";
            favouritesFileName = @"\PlayRandom.ini";

            InitializeApplication();
        }

        internal void InitializeApplication()
        {
            IsBusy = true;

            InitializeCommands();
            InitializeControlsAndData();

            IsBusy = false;
        }
        #endregion Constructors

        #region Events
        private void btnOpenSettings_Click()
        {
            settingsView.ShowDialog();
        }

        private void btnListFav_Click()
        {
            try
            {
                if (IsAddPathChecked) AddPathToList(false);
                if (!Directory.Exists(DirPath)) throw new InvalidOperationException(SEL_VAL_DIR);

                ListFavFiles();
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void btnPlayFav_Click()
        {
            try
            {
                bool refreshListFirstForNewlyAdded = false;

                if (IsAddPathChecked) refreshListFirstForNewlyAdded = AddPathToList(false);
                if (!Directory.Exists(DirPath)) throw new InvalidOperationException(SEL_VAL_DIR);

                SelectAndPlayFav(refreshListFirstForNewlyAdded);
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void tglAddPath_Checked(bool skipValidation)
        {
            try
            {
                AddPathContent = IsAddPathChecked ? "-" : "+";
                AddPathToolTip = "Click to " + (IsAddPathChecked ? "collapse" : "add a new path");

                if (IsAddPathChecked) { DirPathAdded = DirPath; DirPathFocusCommand.Execute(null); }
                else
                {
                    if (DirPathAdded.IsBlank()) skipValidation = true;
                    AddPathToList(skipValidation);
                }
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void btnPlayRandom_Click()
        {
            try
            {
                if (IsAddPathChecked) AddPathToList(false);
                PlayRandom();
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void btnAddSelectedRandomToFav_Click()
        {
            try
            {
                if (!Path.GetFileNameWithoutExtension(SelectedRandomFile.FilePath).StartsWith(PREFIX))
                {
                    var tm = InputTimestamp();
                    if (!tm.IsBlank())
                    {
                        var timestamps = GenerateTimestampList(tm);
                        tm = timestamps.Except(SelectedRandomFile.Timestamps).AppendText(DELIM_TIMESTAMP_FILENAME);

                        switch (AppSettings.FavouritesSettings.FavouritesSaveOption)
                        {
                            case eFavouritesOption.RenameFiles:
                                AddToFavouriteRename(SelectedRandomFile, tm);
                                break;

                            case eFavouritesOption.WriteToDirectory:
                                AddToFavouriteINI(SelectedRandomFile, tm.Split(' '));
                                break;

                            default:
                                throw new NotImplementedException("Action not defined for new Favourites Option..");
                        }

                        if (AppSettings.SearchSettings.AutoSearch) btnPlayRandom_Click();
                    }
                }
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void btnDeleteSelectedRandom_Click()
        {
            try
            {
                if (DeleteFile(SelectedRandomFile.FilePath))
                {
                    AllRandomFiles.Remove(SelectedRandomFile);
                    SelectedRandomFile = AllRandomFiles.FirstOrDefault();

                    if (AppSettings.SearchSettings.AllowRepeat)
                    {
                        int count = -1;
                        if (Int32.TryParse(FileCountText, out count)) RefreshFileCount(count - 1); // Decrement file count on delete, if Repeat is enabled
                    }

                    if (AppSettings.SearchSettings.AutoSearch) btnPlayRandom_Click();
                }
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void btnPlaySelectedRandom_Click()
        {
            try
            {
                PlaySelectedRandomFile();
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void lstFavFiles_MouseDoubleClick()
        {
            try
            {
                if (SelectedFavFile != null) PlayFav(SelectedFavFile);
                else { if (AllFavFiles._None()) return; /* else throw new InvalidOperationException("Please select a favourite!"); */ }
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void btnRenameFav_Click()
        {
            try
            {
                if (SelectedFavFile == null) throw new InvalidOperationException("No files selected!");
                RenameFile();
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void btnDeleteFav_Click()
        {
            try
            {
                if (SelectedFavFile == null) throw new InvalidOperationException("No files selected!");
                if (DeleteFile(SelectedFavFile.FilePath))
                {
                    if (SelectedFavFile.ForeColour == FAV_COLOUR_INI) RemoveFromFavouriteINI(SelectedFavFile);
                    AllFavFiles.Remove(SelectedFavFile);
                }

                RaisePropertyChanged("HasFavourites");
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void cmbFilters_DropDownClosed()
        {
            try
            {
                RefreshFilterTooltip();

                if (!AllFilters.Contains(filterDefault)) AllFilters.Insert(0, filterDefault);
                SelectedFilter = filterDefault;
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void cmbFilters_DropDownOpened()
        {
            try
            {
                if (AllFilters.Contains(filterDefault)) AllFilters.Remove(filterDefault);
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void cmbFilters_SelectionChanged()
        {
            try
            {
                SelectedFilter = filterDefault; // Display selected filter summary when scrolled with keyboard/mouse
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void btnRefreshFilters_Click()
        {
            try
            {
                RefreshFilters(true); // Reset current selection and choose those with files
                cmbFilters_DropDownClosed();
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void chkIncludeFav_Checked()
        {
            try
            {
                IsExpandFavChecked = true; // RKD is this reqd or redundant?
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void mnuCopyPath_Click()
        {
            try
            {
                if (SelectedFavFile == null) return;
                Clipboard.SetText(SelectedFavFile.FilePath);
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void btnDelPath_Click(string filePath)
        {
            try
            {
                SelectedDelPath = filePath;

                AllPaths.Remove(SelectedDelPath);
                if (IsDelPathChecked && !AllPaths.ContainsKey(DirPath)) DirPath = AllPaths.First().Key;

                AllPaths = new Dictionary<string, string>(AllPaths); // RKD binding not working
                AppSettings.SearchSettings.AllPaths = AllPaths.Keys.ToObservableCollection();
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void tglMigrate_MouseEnter()
        {
            IsMigrateContextMenuOpen = true;
        }

        private void mnuUseFolder_Click()
        {
            DirPathAdded = Path.GetDirectoryName(SelectedFavFile.FilePath);
            AddPathToList(false);
        }

        private void mnuFavOpenFolder_Click()
        {
            try
            {
                if (File.Exists(SelectedFavFile.FilePath))
                    Process.Start("explorer.exe", String.Format("/select,\"{0}\"", SelectedFavFile.FilePath));
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void btnBrowse_Click()
        {
            try
            {
                using (WinForms.FolderBrowserDialog dlg = new WinForms.FolderBrowserDialog { Description = "Select directory..", SelectedPath = DirPath, RootFolder = Environment.SpecialFolder.MyComputer, ShowNewFolderButton = false })
                {
                    dlg.ShowDialog();
                    DirPathAdded = dlg.SelectedPath;
                }
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void chkFilterFileType_Checked()
        {
            if (AppSettings.SearchSettings.ApplyFilters) btnRefreshFilters_Click();
        }

        private void btnAll_Click()
        {
            AllFilters.ToList().ForEach(f => f.IsSelected = true);

            cmbFilters_DropDownOpened();
            cmbFilters_DropDownClosed();
        }

        private void btnNone_Click()
        {
            AllFilters.ToList().ForEach(f => f.IsSelected = false);

            cmbFilters_DropDownOpened();
            cmbFilters_DropDownClosed();
        }

        private void mnuFavToDisk_Click()
        {
            try
            {
                var renamedFiles = FavFilesFromRenamed();
                if (!renamedFiles.Any()) { MessageBox.Show("No more files to migrate"); return; }

                foreach (FileDetails fd in renamedFiles)
                {
                    var fileNameWithoutTimestamp = Path.GetFileName(fd.FilePath).Replace(GenerateFileRename(fd.Timestamps, String.Empty), "");
                    var newFilePath = Path.GetDirectoryName(fd.FilePath).AppendPath(fileNameWithoutTimestamp);

                    // 1. Rename file
                    if (File.Exists(newFilePath)) newFilePath = Path.GetDirectoryName(fd.FilePath).AppendPath(Path.GetFileNameWithoutExtension(fileNameWithoutTimestamp) + "_2") + Path.GetExtension(fileNameWithoutTimestamp);
                    File.Move(fd.FilePath, newFilePath);

                    // 2. Add to favourites
                    var fileDetails = new FileDetails(newFilePath);
                    AddToFavouriteINI(fileDetails, fd.Timestamps);

                    if (AppSettings.SearchSettings.AutoSearch) ListFavCommand.Execute(null);
                }
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void mnuFavFileRename_Click()
        {
            try
            {
                var iniFiles = FavFilesFromINI().Where(fl => fl.ForeColour != FAV_COLOUR_ERROR);
                if (!iniFiles.Any()) { MessageBox.Show("No more files to migrate"); return; }

                string favFilePath = Path.GetDirectoryName(DirPath) + favouritesFileName;
                foreach (FileDetails fd in iniFiles)
                {
                    // 1. Remove reference from INI
                    RemoveFromFavouriteINI(fd);

                    // 2. Add to favourites
                    AddToFavouriteRename(fd, fd.Timestamps.Distinct().AppendText(DELIM_TIMESTAMP_FILENAME));

                    if (AppSettings.SearchSettings.AutoSearch) ListFavCommand.Execute(null);
                }
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void lstTimestamp_MouseDoubleClick(string timestampText)
        {
            try
            {
                var startpos = String.Empty;
                startpos = GetStartPos(timestampText, AppSettings.PlaySettings.MediaPlayer);

                if (!startpos.IsBlank()) PlayFavAgainstTimestamp(SelectedFavFile, startpos);
                else throw new NullReferenceException("Timestamp is empty");
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void mnuUpdateTimestamp_Click()
        {
            try
            {
                IsDirty = false;
                timestampBeforeUpdate = SelectedFavFile.Timestamps.AppendText(DELIM_TIMESTAMP_INI);

                IsUpdateMode = true;
                new TimestampSelector(this, SizeToContent.Height).ShowDialog();
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void mnuFavExport_Click()
        {
            try
            {
                WinForms.FolderBrowserDialog dlg = new WinForms.FolderBrowserDialog
                {
                    Description = "Select folder to export to..",
                    RootFolder = Environment.SpecialFolder.Desktop,
                    SelectedPath = AppSettings.FavouritesSettings.ExportFolder,
                    ShowNewFolderButton = true
                };

                if (dlg.ShowDialog() == WinForms.DialogResult.OK)
                {
                    var favFileName = GenerateFileRename(SelectedFavFile.Timestamps, Path.GetFileName(SelectedFavFile.FilePath));
                    File.Copy(SelectedFavFile.FilePath, dlg.SelectedPath.AppendPath(favFileName), true);

                    Process.Start(dlg.SelectedPath);
                    AppSettings.FavouritesSettings.ExportFolder = dlg.SelectedPath;
                }
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void btnAddTimestamp_Click()
        {
            try
            {
                IsTimestampInputBoxOnTop = false;
                var tm = InputTimestamp();
                IsTimestampInputBoxOnTop = true;

                if (!tm.IsBlank())
                {
                    var timestamps = GenerateTimestampList(tm);
                    SelectedFavFile.Timestamps = SelectedFavFile.Timestamps.AddItems(timestamps.Except(SelectedFavFile.Timestamps)).ToObservableCollection();

                    IsDirty = SelectedFavFile.Timestamps.AppendText(DELIM_TIMESTAMP_INI) != timestampBeforeUpdate;
                }
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void btnSortTimestamp_Click()
        {
            try
            {
                SelectedFavFile.Timestamps = IsSortAsc.HasValue ? SelectedFavFile.Timestamps.OrderByDirection(ts => ts, IsSortAsc.Value).ToObservableCollection() : bkpTimestamp;
                IsDirty = SelectedFavFile.Timestamps.AppendText(DELIM_TIMESTAMP_INI) != timestampBeforeUpdate;
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        private void btnSaveTimestamp_Click()
        {
            try
            {
                if (SelectedFavFile.FavReference == eFavouritesOption.RenameFiles)
                {
                    var newFilePath = Path.GetDirectoryName(SelectedFavFile.FilePath).AppendPath(GenerateFileRename(SelectedFavFile.Timestamps, SelectedFavFile.FileName.Split(DELIM_TIMESTAMP_FAVNAME).Last()));
                    File.Move(SelectedFavFile.FilePath, newFilePath); // Update timestamp in file name

                    SelectedFavFile.FilePath = newFilePath;
                    SelectedFavFile.FileName = FormatFavFileName_Ren(SelectedFavFile.FilePath, eFavouritesOption.RenameFiles); // Update collection
                }
                else if (SelectedFavFile.FavReference == eFavouritesOption.WriteToDirectory)
                {
                    RemoveFromFavouriteINI(SelectedFavFile);
                    AddToFavouriteINI(SelectedFavFile, SelectedFavFile.Timestamps);

                    SelectedFavFile.FileName = FormatFavFileName_INI(SelectedFavFile, eFavouritesOption.WriteToDirectory); // Update collection
                }
                else throw new NotImplementedException("Action not defined for new Favourites Option..");
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
            finally
            {
                IsDirty = false;
                if (AppSettings.FavouritesSettings.SaveClose) CloseTimestampWindowCommand.Execute(null);
            }
        }

        private void btnDeleteTimestamp_Click(string timestamp)
        {
            try
            {
                SelectedFavFile.Timestamps.Remove(timestamp);
                IsDirty = SelectedFavFile.Timestamps.AppendText(DELIM_TIMESTAMP_INI) != timestampBeforeUpdate;
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }
        #endregion Events

        #region Methods
        private void InitializeCommands()
        {
            OpenSettingsCommand = new RelayCommand((parameter) => { btnOpenSettings_Click(); });

            ListFavCommand = new RelayCommand((parameter) => { btnListFav_Click(); });
            PlayFavCommand = new RelayCommand((parameter) => { btnPlayFav_Click(); });

            AddPathCommand = new RelayCommand((parameter) => { tglAddPath_Checked(false); });
            PlayRandomCommand = new RelayCommand((parameter) => { btnPlayRandom_Click(); });

            AddSelectedRandomToFavCommand = new RelayCommand((parameter) => { btnAddSelectedRandomToFav_Click(); });
            DeleteSelectedRandomCommand = new RelayCommand((parameter) => { btnDeleteSelectedRandom_Click(); });
            PlaySelectedRandomCommand = new RelayCommand((parameter) => { btnPlaySelectedRandom_Click(); });

            FavFilesMouseDoubleClickCommand = new RelayCommand((parameter) => { lstFavFiles_MouseDoubleClick(); });
            RenameFavCommand = new RelayCommand((parameter) => { btnRenameFav_Click(); });
            DeleteFavCommand = new RelayCommand((parameter) => { btnDeleteFav_Click(); });
            FiltersDropDownClosedCommand = new RelayCommand((parameter) => { cmbFilters_DropDownClosed(); });
            FiltersDropDownOpenedCommand = new RelayCommand((parameter) => { cmbFilters_DropDownOpened(); });
            FiltersSelectionChangedCommand = new RelayCommand((parameter) => { cmbFilters_SelectionChanged(); });
            RefreshFiltersCommand = new RelayCommand((parameter) => { btnRefreshFilters_Click(); });
            IncludeFavCheckedCommand = new RelayCommand((parameter) => { chkIncludeFav_Checked(); });
            CopyPathCommand = new RelayCommand((parameter) => { mnuCopyPath_Click(); });
            DelPathCommand = new RelayCommand<string>((parameter) => { btnDelPath_Click(parameter); });
            MigrateMouseEnterCommand = new RelayCommand((parameter) => { tglMigrate_MouseEnter(); });

            UseFolderCommand = new RelayCommand((parameter) => { mnuUseFolder_Click(); });
            FavOpenFolderCommand = new RelayCommand((parameter) => { mnuFavOpenFolder_Click(); });
            BrowseCommand = new RelayCommand((parameter) => { btnBrowse_Click(); });

            FilterFileTypeCheckedCommand = new RelayCommand((parameter) => { chkFilterFileType_Checked(); });
            FilterAllCommand = new RelayCommand((parameter) => { btnAll_Click(); });
            FilterNoneCommand = new RelayCommand((parameter) => { btnNone_Click(); });

            UpdateTimestampCommand = new RelayCommand((parameter) => { mnuUpdateTimestamp_Click(); });
            AddTimestampCommand = new RelayCommand((parameter) => { btnAddTimestamp_Click(); });
            SortTimestampCommand = new RelayCommand((parameter) => { btnSortTimestamp_Click(); });
            SaveTimestampCommand = new RelayCommand((parameter) => { btnSaveTimestamp_Click(); });
            DeleteTimestampCommand = new RelayCommand<string>((parameter) => { btnDeleteTimestamp_Click(parameter); });

            FavToDiskCommand = new RelayCommand((parameter) => { mnuFavToDisk_Click(); });
            FavFileRenameCommand = new RelayCommand((parameter) => { mnuFavFileRename_Click(); });
            FavExportCommand = new RelayCommand((parameter) => { mnuFavExport_Click(); });

            TimestampMouseDoubleClickCommand = new RelayCommand<string>((parameter) => { lstTimestamp_MouseDoubleClick(parameter); });
        }

        private void InitializeControlsAndData()
        {
            tglAddPath_Checked(true);

            SaveSettings += SaveSettingsToFile;
            LoadSettingsFromFile();

            AllMediaPlayers = Enum.GetValues(typeof(eMediaPlayer)).Cast<eMediaPlayer>()
                .ToDictionary(med => med, med => (med.GetEnumAttribute<eMediaPlayer>("Description") as DescriptionAttribute).Description + (med == new eMediaPlayer() ? " (default)" : ""));
            AllDeleteOptions = Enum.GetValues(typeof(eRecycleOption)).Cast<eRecycleOption>()
                .ToDictionary(med => med, med => (med.GetEnumAttribute<eRecycleOption>("Description") as DescriptionAttribute).Description + (med == new eRecycleOption() ? " (default)" : ""));
            AllFavouritesOptions = Enum.GetValues(typeof(eFavouritesOption)).Cast<eFavouritesOption>()
                .ToDictionary(med => med, med => (med.GetEnumAttribute<eFavouritesOption>("Description") as DescriptionAttribute).Description + (med == new eFavouritesOption() ? " (default)" : ""));

            IsDeleteFavEnabled = _SelectedFavFile != null;

            AllFilters = GetDefaultFilters();
            cmbFilters_DropDownClosed();
        }

        private bool AddPath(string path)
        {
            if (!AllPaths.ContainsKey(FormatPath(path)))
            {
                var paths = AllPaths.ToList();
                paths.Insert(0, new KeyValuePair<string, string>(FormatPath(path), null));
                AllPaths = paths.ToDictionary(p => p.Key, p => p.Value);

                AppSettings.SearchSettings.AllPaths = AllPaths.Keys.ToObservableCollection();
                return true;
            }

            return false;
        }

        /// <summary>
        /// Hides path-edit box and adds path to list
        /// </summary>
        private bool AddPathToList(bool skipValidation)
        {
            if (!IsAddPathChecked && skipValidation) return false; // No action if not in Edit mode
            if (!Directory.Exists(DirPathAdded)) throw new InvalidOperationException(SEL_VAL_DIR);

            IsDelPathChecked = IsAddPathChecked = false;
            DirPath = AddPath(DirPathAdded) ? AllPaths.FirstOrDefault().Key : DirPathAdded;

            return true;
        }

        private static string FormatPath(string path)
        {
            return path.IsBlank() ? path : path.Trim('\\') + @"\";
        }

        private void PlayRandom()
        {
            DateTime searchStart = DateTime.Now;

            FilePath_Org = RandomizeFilePath(PATTERN_ALL, AppSettings.SearchSettings.IncludeFavourites, AppSettings.SearchSettings.AllowRepeat, AppSettings.SearchSettings.ApplyFilters);
            if (FilePath_Org.IsBlank())
            {
                MessageBox.Show(String.Format("No {0}files found!\n{1}", AllRandomFiles.Any() ? "NEW " : "", AllRandomFiles.Any() ? "Enable REPEAT flag or allow FAV.." : "Choose another path or allow FAV.."), "Information..");
                return;
            }

            var fileExisiting = AllRandomFiles.Where(f => f.FilePath == FilePath_Org).FirstOrDefault();
            if (fileExisiting != null) // File already added
            {
                if (AppSettings.SearchSettings.AddFilesOnTop) // Move file on top
                {
                    AllRandomFiles.Remove(fileExisiting);
                    AllRandomFiles.Insert(0, new FileDetails(FilePath_Org));
                }
            }
            else AllRandomFiles.Insert(0, new FileDetails(FilePath_Org));

            StatusAddnlInfo = "Search completed in " + Convert.ToDateTime((DateTime.Now - searchStart).ToString()).ToString("mm:ss.fff");

            SelectedRandomFile = AllRandomFiles.Where(f => f.FilePath == FilePath_Org).FirstOrDefault();
            PlaySelectedRandomFile();
        }

        private void PlaySelectedRandomFile()
        {
            if (!IsAdminNoPlay)
                switch (AppSettings.PlaySettings.MediaPlayer)
                {
                    case eMediaPlayer.MPC:
                        PlayInMPCHC(SelectedRandomFile.FilePath); break;

                    case eMediaPlayer.VLC:
                        PlayInVLC(SelectedRandomFile.FilePath); break;

                    case eMediaPlayer.Default:
                        Process.Start(SelectedRandomFile.FilePath); break;

                    default:
                        throw new NotImplementedException("Action not defined for new player..");
                }
        }

        private void ListFavFiles()
        {
            IsExpandFavChecked = true;

            if (SelectedFavFile != null) lastSelectedFavFile = SelectedFavFile.FilePath; // backup fav file
            lastPathForListFav = DirPath; // backup search directory

            var others = FavFilesFromINI(); // Favourites from INI file
            var renamedFiles = FavFilesFromRenamed(); // Favourites from RENAMED files

            AllFavFiles = renamedFiles.Union(others).ToObservableCollection();
            RaisePropertyChanged("HasFavourites");

            FileSearchText = String.Empty;
            SelectedFavFile = AllFavFiles_Filtered.Where(fl => fl.FilePath == lastSelectedFavFile).FirstOrDefault(); // restore fav file
        }

        private IEnumerable<FileDetails> FavFilesFromINI()
        {
            // Favourites from INI file
            var fileList = GetFavFileListFromConfig();
            return fileList.Select(f => new FileDetails(f.Key, FormatFavFileName_Ren(GeneratePseudoPathInOldFormat(f.Key, f.Value.Split(DELIM_TIMESTAMP_INI).Distinct()), eFavouritesOption.RenameFiles), f.Value.IsBlank() || !File.Exists(f.Key) ? FAV_COLOUR_ERROR : FAV_COLOUR_INI) { Timestamps = f.Value.Split(DELIM_TIMESTAMP_INI).Distinct().ToObservableCollection(), FavReference = eFavouritesOption.WriteToDirectory });
        }

        private IEnumerable<FileDetails> FavFilesFromRenamed()
        {
            // Favourites from RENAMED files
            return GetAllFilePaths(PATTERN_FAV, true, true, false, false) // Don't refresh filter list as not applicable
                .Select(f => new FileDetails(f, FormatFavFileName_Ren(f, eFavouritesOption.RenameFiles), FAV_COLOUR_RENAMED) { Timestamps = GetTimestampsFromRenamedFileNames(f), FavReference = eFavouritesOption.RenameFiles });
        }

        private string InputTimestamp()
        {
            return Interaction.InputBox("Enter time in mm:ss format:\r\n( separate by [" + DELIM_TIMESTAMP_INPUT + "] )", "Awaiting input..", "00:00").Trim();
        }

        private IEnumerable<string> GenerateTimestampList(string tm)
        {
            return tm.Split(DELIM_TIMESTAMP_INPUT.ToCharArray())
                .Where(z => !z.IsBlank())
                .Select(z => { return z.Length < 4 ? z.RemoveText(":").PadLeft(4, '0') : z; });
        }

        /// <summary>
        /// Renames file to favourite format
        /// </summary>
        /// <param name="fileDetails">File details</param>
        /// <param name="tm">Space delimited timestamps without colon</param>
        private void AddToFavouriteRename(FileDetails fileDetails, string tm)
        {
            var newFileName = String.Format("{0} {1} {2}", PREFIX, tm.Replace(":", ""), Path.GetFileNameWithoutExtension(fileDetails.FilePath));
            var newFilePath = fileDetails.FilePath.Replace(Path.GetFileNameWithoutExtension(fileDetails.FilePath), newFileName);

            File.Move(fileDetails.FilePath, newFilePath);

            fileDetails.FileName = newFileName;
            fileDetails.FilePath = newFilePath;
        }

        private void AddToFavouriteINI(FileDetails fileDetails, IEnumerable<string> timestamps)
        {
            string favFilePath = ValidateFavFile(fileDetails);

            var tm = timestamps.AppendText(DELIM_TIMESTAMP_FILENAME).Replace(":", "").Replace(" ", "|");
            File.AppendAllLines(favFilePath, new string[] { String.Format("{0}<delim>{1}<delim>{2}<delim>{3}".Replace("<delim>", DELIM_FIELDS.ToString()), Path.GetFileName(fileDetails.FilePath), tm, new MemoryMgmt(eByteValueType.B, fileDetails.FileSize).PracticalSize, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")) });

            fileDetails.FileName = Path.GetFileName(GeneratePseudoPathInOldFormat(fileDetails.FilePath, tm.Split(DELIM_TIMESTAMP_INI)));
        }

        private string ValidateFavFile(FileDetails fileDetails)
        {
            return ValidateFavFile(Path.GetDirectoryName(fileDetails.FilePath));
        }

        private string ValidateFavFile(string favFileDirectory)
        {
            string favFilePath = favFileDirectory + favouritesFileName;
            if (!File.Exists(favFilePath)) File.WriteAllText(favFilePath, String.Empty); // Create file if not available
            File.SetAttributes(favFilePath, File.GetAttributes(favFilePath) | FileAttributes.Hidden); // Hide file

            return favFilePath;
        }

        private void RemoveFromFavouriteINI(FileDetails fileDetails)
        {
            string favFilePath = Path.GetDirectoryName(DirPath) + favouritesFileName;
            if (File.Exists(favFilePath))
            {
                try
                {
                    var sett = File.ReadAllLines(favFilePath).Where(ln => !ln.StartsWith(Path.GetFileName(fileDetails.FilePath))).Distinct();

                    File.Move(favFilePath, Path.ChangeExtension(favFilePath.Replace(Path.GetFileName(favFilePath), Path.GetFileNameWithoutExtension(favFilePath) + DateTime.Now.ToString("_yyyyMMdd_HHmmss")), "bak")); // Back up file
                    ValidateFavFile(fileDetails); // Create new file

                    File.AppendAllLines(favFilePath, sett); // Write settings
                }
                catch (Exception) { MessageBox.Show("Blew right back to square ONE"); }
            }
        }

        private Dictionary<string, string> GetFavFileListFromConfig()
        {
            var fileList = new Dictionary<string, string>();
            var configFiles = Directory.GetFiles(DirPath, "*" + favouritesFileName.TrimStart('\\'), AppSettings.FavouritesSettings.SearchSubDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
                .Where(f => Path.GetFileName(f) == favouritesFileName.TrimStart('\\'));
            foreach (var cfg in configFiles)
            {
                DateTime dt;

                var lines = File.ReadAllLines(cfg);
                var files = lines.Select(ln => new
                {
                    FilePath = ln.Split(DELIM_FIELDS).FirstOrDefault(),
                    Timestamp = ln.Split(DELIM_FIELDS).ElementAtOrDefault(1).Split(DELIM_TIMESTAMP_INI),
                    ModifiedOn = DateTime.TryParse(ln.Split(DELIM_FIELDS).LastOrDefault(), out dt) ? dt : DateTime.MinValue
                });

                var dupeFiles = from f in files
                                group f by f.FilePath into grp
                                where grp.Count() > 1
                                select new { FilePath = grp.Key, Count = grp.Count(), LastModified = files.Where(y => y.FilePath == grp.Key).Max(y => y.ModifiedOn) };

                var dupes = new Dictionary<string, string>();
                if (dupeFiles.Any())
                {
                    foreach (var m in dupeFiles)
                    {
                        var dupe = m.FilePath;

                        var z = lines.Where(ln => ln.StartsWith(dupe));
                        if (AppSettings.FavouritesSettings.TakeLatestTimestamp) z = z.Where(ln => Convert.ToDateTime(ln.Split(DELIM_FIELDS).LastOrDefault()) == m.LastModified);

                        var z2 = z.Select(ln => ln.Split(DELIM_FIELDS).ElementAtOrDefault(1));
                        dupes.Add(Path.GetDirectoryName(cfg).AppendPath(dupe), z2.AppendText(DELIM_TIMESTAMP_INI.ToString()));
                    }
                }

                var dict = lines.Where(ln => !dupes.ContainsKey(Path.GetDirectoryName(cfg).AppendPath(ln.Split(DELIM_FIELDS).FirstOrDefault()))) //.Union(dupes)
                    .ToDictionary(ln => Path.GetDirectoryName(cfg).AppendPath(ln.Split(DELIM_FIELDS).FirstOrDefault()), ln => ln.Split(DELIM_FIELDS).ElementAtOrDefault(1));
                dict = dict.Union(dupes).ToDictionary(d => d.Key, d => d.Value);

                fileList = fileList.Union(dict).ToDictionary(fl => fl.Key, fl => fl.Value.Coalesce()); // Append to existing list
            }

            return fileList;
        }

        private void SelectAndPlayFav(bool refreshListFirst)
        {
            if (SelectFav(refreshListFirst)) PlayFav(SelectedFavFile);
        }

        private bool SelectFav(bool refreshListFirst)
        {
            IsExpandFavChecked = true;

            if (refreshListFirst || DirPath != lastPathForListFav) ListFavFiles();

            if (SelectedFavFile == null) SelectedFavFile = AllFavFiles_Filtered.ElementAtOrDefault(GenerateRandomIndex(AllFavFiles_Filtered.Count()));
            else
            {
                var selectedFavFile = AllFavFiles_Filtered.ElementAt(GenerateRandomIndex(AllFavFiles_Filtered.Count()));
                if (AppSettings.FavouritesSettings.RepeatFavourites) SelectedFavFile = selectedFavFile;
                else
                {
                    while (selectedFavFile.ForeColour == FAV_COLOUR_PLAYED)
                    {
                        selectedFavFile = AllFavFiles_Filtered.ElementAt(GenerateRandomIndex(AllFavFiles_Filtered.Count()));
                        if (!AllFavFiles_Filtered.Where(fav => fav.ForeColour != FAV_COLOUR_PLAYED).Any()) // No more files left
                        {
                            if (MessageBox.Show("All favourites have been played! Allow repeats?", "Confirmation..", MessageBoxButton.YesNo) != MessageBoxResult.Yes) return false; // Exit loop
                            RepeatFavourites = true;

                            break;
                        }
                    }

                    SelectedFavFile = selectedFavFile;
                }
            }

            ScrollToViewCommand.Execute(SelectedFavFile);
            return true;
        }

        private void PlayFav(FileDetails selectedFavFile)
        {
            if (selectedFavFile == null) return;

            if (selectedFavFile.ForeColour == FAV_COLOUR_ERROR)
            {
                MessageBox.Show(String.Format("Invalid favourite file settings\r\n\r\n{0}\r\n  {1}{2}", selectedFavFile.FileName.Split(DELIM_TIMESTAMP_FAVNAME).First(), DELIM_TIMESTAMP_FAVNAME, selectedFavFile.FileName.Split(DELIM_TIMESTAMP_FAVNAME).Last()), "An error occurred..");
                return;
            }

            selectedFavFile.ForeColour = FAV_COLOUR_PLAYED;
            FilterFav(FileSearchText);

            if (selectedFavFile.Timestamps.Count() > 1)
            {
                TimestampMediaPlayer = (AppSettings.PlaySettings.MediaPlayer.GetEnumAttribute<eMediaPlayer>("Description") as DescriptionAttribute).Description;
                IsUpdateMode = false;

                new TimestampSelector(this, SizeToContent.WidthAndHeight).ShowDialog();
            }
            else
            {
                string filePath = selectedFavFile.FilePath;
                if (selectedFavFile.FavReference == eFavouritesOption.WriteToDirectory)
                    filePath = GeneratePseudoPathInOldFormat(selectedFavFile);

                var startpos = GetStartPos(Path.GetFileNameWithoutExtension(filePath).Split(DELIM_TIMESTAMP_FILENAME)[1], AppSettings.PlaySettings.MediaPlayer);
                PlayFavAgainstTimestamp(selectedFavFile, startpos);
            }
        }

        internal void PlayFavAgainstTimestamp(FileDetails selectedFavFile, string startpos)
        {
            switch (AppSettings.PlaySettings.MediaPlayer)
            {
                case eMediaPlayer.Default:
                    Process.Start(selectedFavFile.FilePath);
                    break;

                case eMediaPlayer.MPC:
                    PlayInMPCHC(selectedFavFile.FilePath, startpos);
                    break;

                case eMediaPlayer.VLC:
                    PlayInVLC(selectedFavFile.FilePath, startpos);
                    break;

                default:
                    throw new NotImplementedException("Action not defined for new player..");
            }
        }

        private void PlayInVLC(string filePath, string startpos = "0")
        {
            if (!IsAdminNoPlay)
                Process.Start(PATH_VLC, String.Format("\"{0}\" {1} --start-time={2}", filePath, AppSettings.PlaySettings.PlayFullscreen ? "-f" : "", startpos)); // https://wiki.videolan.org/Documentation:Command_line/
        }

        private void PlayInMPCHC(string filePath, string startpos = "00:00:00")
        {
            if (!IsAdminNoPlay)
                Process.Start(PATH_MPCHC, String.Format("\"{0}\" {1} /startpos {2}", filePath, AppSettings.PlaySettings.PlayFullscreen ? "/fullscreen" : "", startpos)); // cmd > mpc-hc /?
        }

        /// <summary>
        /// Make sure this is in [mm:ss] or [mmss] formats only, DONT ADD HH part
        /// </summary>
        /// <param name="timestamp"></param>
        /// <param name="mediaPlayer"></param>
        /// <returns></returns>
        internal static string GetStartPos(string timestamp, eMediaPlayer mediaPlayer)
        {
            switch (mediaPlayer)
            {
                case eMediaPlayer.MPC:
                    timestamp = String.Format("00:{0}:{1}", Strings.Left(timestamp, 2), Strings.Right(timestamp, 2));
                    break;

                case eMediaPlayer.VLC:
                    timestamp = (Convert.ToInt32(Strings.Left(timestamp, 2)) * 60 + Convert.ToInt32(Strings.Right(timestamp, 2))).ToString();
                    break;

                default:
                    throw new NotImplementedException("Action not defined for new player..");
            }

            return timestamp;
        }

        private string FormatFavFileName_Ren(string filePath, eFavouritesOption favouritesOption)
        {
            switch (favouritesOption)
            {
                case eFavouritesOption.RenameFiles:
                    var fileName = Path.GetFileName(filePath);
                    var tstamp1 = GetTimestampsFromRenamedFileNames(fileName).Select(ts => GetStartPos(ts, eMediaPlayer.MPC).Substring(3, 5)).AppendText(DELIM_TIMESTAMP_FILENAME);

                    return tstamp1 + " » " + Path.GetFileName(filePath).RemoveText(PREFIX + " " + tstamp1.RemoveText(":")).Trim();

                case eFavouritesOption.WriteToDirectory:
                default:
                    throw new NotImplementedException("Action not defined for Favourites Option: " + favouritesOption.ToString());
            }
        }

        private string FormatFavFileName_INI(FileDetails fileDetails, eFavouritesOption favouritesOption)
        {
            switch (favouritesOption)
            {
                case eFavouritesOption.WriteToDirectory:
                    var tstamp2 = fileDetails.Timestamps.Select(ts => GetStartPos(ts, eMediaPlayer.MPC).Substring(3, 5)).AppendText(DELIM_TIMESTAMP_FILENAME);
                    return tstamp2 + " » " + Path.GetFileName(fileDetails.FilePath);

                case eFavouritesOption.RenameFiles:
                default:
                    throw new NotImplementedException("Action not defined for Favourites Option: " + favouritesOption.ToString());
            }
        }

        private ObservableCollection<string> GetTimestampsFromRenamedFileNames(string filePath)
        {
            return Path.GetFileNameWithoutExtension(filePath).Split(DELIM_TIMESTAMP_FILENAME).RemoveItem(PREFIX)
                .TakeWhile(f => f.RemoveText(":").IsNumeric())
                .Where(f => ValidateTimestampFormat(f)).Distinct().ToObservableCollection();
        }

        private string GeneratePseudoPathInOldFormat(FileDetails selectedFavFile)
        {
            return GeneratePseudoPathInOldFormat(selectedFavFile.FilePath, selectedFavFile.Timestamps);
        }

        private string GeneratePseudoPathInOldFormat(string filePath, IEnumerable<string> timestamp)
        {
            return Path.GetDirectoryName(filePath) + "\\" + GenerateFileRename(timestamp, Path.GetFileName(filePath));
        }

        private string GenerateFileRename(IEnumerable<string> timestamp, string fileName)
        {
            return PREFIX + " " + timestamp.Distinct().AppendText(DELIM_TIMESTAMP_FILENAME).Trim() + " " + fileName.Trim();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pattern"></param>
        /// <param name="includeFav"></param>
        /// <param name="allowRepeat"></param>
        /// <param name="refreshFilters">TRUE: Refreshes filters in list and select those with files</param>
        /// <returns></returns>
        private ObservableCollection<string> GetAllFilePaths(string pattern, bool includeFav, bool allowRepeat, bool refreshFilters, bool resetFilters)
        {
            ObservableCollection<string> files = Directory.GetFiles(DirPath, pattern, AppSettings.SearchSettings.SearchSubDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
                .Where(f => !IGNORE_EXTN.ToUpper().Split(',').Contains(Path.GetExtension(f).Trim('.').ToUpper())).ToObservableCollection();

            if (refreshFilters) { RefreshFilters(resetFilters); cmbFilters_DropDownClosed(); }

            if (!includeFav) files = files.Where(f => !Path.GetFileNameWithoutExtension(f).StartsWith(PREFIX) && !GetFavFileListFromConfig().ContainsKey(f)).ToObservableCollection();
            if (!allowRepeat) files = files.Where(f => !AllRandomFiles.Select(z => z.FilePath).Contains(f)).ToObservableCollection();

            return files;
        }

        private string RandomizeFilePath(string pattern, bool includeFav, bool allowRepeat, bool applyFilters)
        {
            // For current call
            StatusText = String.Format("Showing results from {0}.. [ Cache threshold: {1}, Auto: {2} ]",
                AppSettings.SearchSettings.CacheSearch && files_bkp != null ? "cache" : "disk",
                AppSettings.SearchSettings.CacheLimit, AppSettings.SearchSettings.AutoCache ? "ON" : "OFF");

            ObservableCollection<string> files = null;
            if (files_bkp != null && AppSettings.SearchSettings.CacheSearch) files = files_bkp.ToObservableCollection();
            else
            {
                files = GetAllFilePaths(pattern, includeFav, allowRepeat, true, AllFilters.Where(f => f.Count > 0 && !f.IsSelected)._None()); // Refresh filter list but dont reset if some not selected
                files_bkp = files.ToObservableCollection();
            }

            // For next call
            if (AppSettings.SearchSettings.AutoCache)
                AppSettings.SearchSettings.CacheSearch = (files.Count() - 1 > AppSettings.SearchSettings.CacheLimit); // Auto turn on/off based on limit

            var filters = AllFilters.Where(f => f.IsSelected).Select(f => f.FileExtn);
            if (applyFilters) files = files.Where(f => filters.Contains(Path.GetExtension(f).Trim('.').ToUpper())).ToObservableCollection();

            RefreshFileCount(files.Count() + (allowRepeat || files._None() ? 0 : -1)); // Display remaining file count, same if Repeat is enabled

            // Randomize file
            int randomIndex = GenerateRandomIndex(files.Count());
            var randomFilePath = files.ElementAtOrDefault(randomIndex);

            files_bkp.Remove(randomFilePath);
            return randomFilePath;
        }

        private void RefreshFileCount(int fileCount)
        {
            FileCountText = fileCount.ToString();
        }

        private static int GenerateRandomIndex(int count)
        {
            return new Random().Next(0, count);
        }

        private void RenameFile()
        {
            if (SelectedFavFile.FavReference == eFavouritesOption.RenameFiles)
            {
                var newFileName = Interaction.InputBox("Enter new file name", "File rename prompt..", Path.GetFileNameWithoutExtension(SelectedFavFile.FileName.Split(DELIM_TIMESTAMP_FAVNAME).Last()).Trim()).Trim();
                if (!newFileName.IsBlank())
                {
                    var newFilePath = Path.GetDirectoryName(SelectedFavFile.FilePath).AppendPath(GenerateFileRename(SelectedFavFile.Timestamps, newFileName)) + Path.GetExtension(SelectedFavFile.FilePath);
                    File.Move(SelectedFavFile.FilePath, newFilePath); // Rename file

                    SelectedFavFile.FilePath = newFilePath;
                    SelectedFavFile.FileName = FormatFavFileName_Ren(SelectedFavFile.FilePath, eFavouritesOption.RenameFiles); // Update collection 
                }
            }
            else if (SelectedFavFile.FavReference == eFavouritesOption.WriteToDirectory)
            {
                var newFileName = Interaction.InputBox("Enter new file name", "File rename prompt..", Path.GetFileNameWithoutExtension(SelectedFavFile.FilePath)).Trim();
                if (!newFileName.IsBlank())
                {
                    var newFilePath = Path.GetDirectoryName(SelectedFavFile.FilePath).AppendPath(newFileName) + Path.GetExtension(SelectedFavFile.FilePath);
                    File.Move(SelectedFavFile.FilePath, newFilePath); // Rename file

                    // Update INI
                    RemoveFromFavouriteINI(SelectedFavFile);

                    SelectedFavFile.FilePath = newFilePath;
                    AddToFavouriteINI(SelectedFavFile, SelectedFavFile.Timestamps);

                    SelectedFavFile.FileName = FormatFavFileName_INI(SelectedFavFile, eFavouritesOption.WriteToDirectory); // Update collection 
                }
            }
            else throw new NotImplementedException("Action not defined for new Favourites Option..");
        }

        private bool DeleteFile(string filePath)
        {
            try
            {
                var recycleOption = (FileIO.RecycleOption)Enum.Parse(typeof(FileIO.RecycleOption), AppSettings.DeleteSettings.RecycleOption.ToString());
                FileIO.FileSystem.DeleteFile(filePath, FileIO.UIOption.AllDialogs, recycleOption, FileIO.UICancelOption.ThrowException);

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        private ObservableCollection<Filter> GetDefaultFilters()
        {
            return new ObservableCollection<Filter>("mp4,mov,mkv,".ToUpper().Split(',').Distinct()
                .Where(f => !f.IsBlank()).OrderBy(f => f).Select(f => new Filter(f, true)));
        }

        /// <summary>
        /// Refreshes filters in dropdown and check those having files
        /// </summary>
        /// <param name="resetSelection">TRUE: Reset current selection of filters</param>
        private void RefreshFilters(bool resetSelection)
        {
            var selectedFilters = AllFilters.Where(f => f.IsSelected).Select(f => f.FileExtn).ToObservableCollection();

            var defaults = GetDefaultFilters();
            var extensions = GetAllFilePaths(PATTERN_ALL, AppSettings.SearchSettings.IncludeFavourites, AppSettings.SearchSettings.AllowRepeat, false, resetSelection) // Don't reload filter list
                .GroupBy(f => Path.GetExtension(f).Trim('.').ToUpper())
                .Select(f => new Filter { FileExtn = f.Key, Count = f.Count(), IsSelected = f.Any() });

            var extra = defaults.Where(f => !extensions.Select(z => z.FileExtn).Contains(f.FileExtn));
            var allFilters = extensions.Union(extra).ToObservableCollection();

            if (resetSelection) allFilters.ToList().ForEach(f => f.IsSelected = (f.Count > 0));
            else allFilters.ToList().ForEach(f => f.IsSelected = selectedFilters.Contains(f.FileExtn));

            AllFilters = new ObservableCollection<Filter>(allFilters.OrderBy(f => f.FileExtn));
        }

        private void RefreshFilterTooltip()
        {
            string filters = String.Empty;
            filters = AllFilters.AppendText(f => f.FileExtn, DELIM_FILTERS, f => f != filterDefault && f.IsSelected);

            string avlbl = String.Empty;
            avlbl = AllFilters.AppendText(f => f.FileExtn, DELIM_FILTERS, f => f != filterDefault && !f.IsSelected);

            FiltersToolTip = (filters.IsBlank() ? "No filters selected!" : "Filters: " + filters.Trim(DELIM_FILTERS.ToCharArray()))
                + Environment.NewLine
                + (avlbl.IsBlank() ? "All filters selected!" : "Available: " + avlbl.Trim(DELIM_FILTERS.ToCharArray()));

            filterDefault.FileExtn = filters.IsBlank()
                ? "No filters selected!"
                : (avlbl.IsBlank() ? "All filters selected!" : filters.Trim(DELIM_FILTERS.ToCharArray()));
        }

        private void LoadSettingsFromFile()
        {
            try
            {
                try
                {
                    SettingsText = File.Exists(settingsPath) ? File.ReadAllText(settingsPath) : String.Empty;
                    AppSettings = Serializer.DeSerializeFromFormattedData<Settings>(SettingsText, eSerializationFormat.JSon, true);
                }
                finally { if (AppSettings == null) AppSettings = new Settings(); }

                AllPaths = AppSettings.SearchSettings.AllPaths.ToDictionary(p => p, null);
                DirPath = AppSettings.SearchSettings.DirPath;

                RaisePropertyChanged("AppSettings");
            }
            catch (Exception ex)
            {
                LogError(ex);
                AppSettings = new Settings();
            }
        }

        private void SaveSettingsToFile()
        {
            try
            {
                var serializedSettings = Serializer.SerializeToFormattedData<Settings>(AppSettings, eSerializationFormat.JSon).Replace(",", ", ").Replace("}, \"", "},\r\n\"");
                if (serializedSettings != SettingsText)
                {
                    AppSettings.LastSaved = DateTime.Now.ToString(DATETIME_FORMAT);
                    serializedSettings = Serializer.SerializeToFormattedData<Settings>(AppSettings, eSerializationFormat.JSon).Replace(",", ", ").Replace("}, \"", "},\r\n\"");

                    File.WriteAllText(settingsPath, serializedSettings); // Save settings only when changed
                }
            }
            catch (Exception ex)
            {
                LogError(ex);
            }
        }

        bool ValidateTimestampFormat(string f)
        {
            int val = -1;
            return (f != PREFIX && f.Length == 4 && Int32.TryParse(f, out val));
        }

        private void FilterFav(string fileSearchText)
        {
            if (SelectedFavFile != null) lastSelectedFavFile = SelectedFavFile.FilePath; // backup fav file
            AllFavFiles_Filtered = AllFavFiles.Where(fl => fileSearchText.IsBlank() || fileSearchText == WatermarkText ? true : fl.FileName.ToUpper().Contains(fileSearchText.ToUpper())).ToObservableCollection();
            SelectedFavFile = AllFavFiles_Filtered.Where(fl => fl.FilePath == lastSelectedFavFile).FirstOrDefault(); // restore fav file

            FavCountText = AllFavFiles.Count().ToString();
            RemFavCountText = AllFavFiles_Filtered.Where(fav => AppSettings.FavouritesSettings.RepeatFavourites || fav.ForeColour != FAV_COLOUR_PLAYED).Count().ToString();
            FilteredFavCountText = AllFavFiles_Filtered.Count().ToString();
        }
        #endregion Methods

        #region ILogger members
        public void LogError(Exception ex)
        {
            StackTrace trace = new StackTrace(ex.InnerException.Coalesce(ex), true);
            StackFrame frame = trace.GetFrame(0); // RKD Make more dynamic instead of hard-coding frames to skip

            var msg = String.Format("File\t{0}\nMethod\t{1}\nLine\t{2}, {3}", Path.GetFileNameWithoutExtension(frame.GetFileName()), frame.GetMethod().Name, frame.GetFileLineNumber(), frame.GetFileColumnNumber());
            MessageBox.Show(String.Format("{0}\n\n{1}", ex.Message.PadRight(150, ' '), msg), "An error occurred..");
        }
        #endregion ILogger members
    }
}
